// =====================================================================================
// 
//       Filename:  PyInvoker.cpp
// 
//    Description: call embedding python module
// 
//        Version:  1.0
//       Revision:  none
//       Compiler:  g++
// 
//         Author:  Lee Jae Joon (jjlee), jjlee@nhn.com
//        Company:  NHN
// 
// =====================================================================================

#include <Python.h>
#include <iostream>
#include <string>
#include "PyInvoker.h"
#include "PyException.h"

PyInvoker::PyInvoker(const string path, const string moduleName):moduleRef(NULL), module(moduleName)
{
	Py_Initialize();
	addPath(path);
	import(moduleName);
}

PyInvoker::~PyInvoker()
{
	Py_DECREF(getModuleRef());
	map<string, PyObject *>::iterator it = pyClassMap.begin();
	for (; it != pyClassMap.end(); it++) 
	{
		Py_DECREF(it->second);
	}

	Py_Finalize();
}

void PyInvoker::addPath(const string& path)
{
	cout << "add path (" << path <<")" <<  endl;
	PyObject* sysPath = PySys_GetObject((char*)"path");
	if (sysPath == NULL) {
		PyErr_Print();
		THROW_M(PyException, __func__);
	}
	PyList_Append(sysPath, PyString_FromString(path.c_str()));
}

void PyInvoker::import(const string& moduleName)
{
	PyObject* module = PyImport_Import(PyString_FromString(moduleName.c_str()));
	if (module == NULL)
	{
		cerr << "PyImport_Import failed (" << moduleName << ".)" << endl;
		PyErr_Print();
		THROW_M(PyException, __func__);
	}

	setModuleRef(module);
}

void PyInvoker::run(const string className, const string initArgs, const string methodName, const string args)
{
	constructor(className, initArgs);
	callMethod(className, methodName, args);
}

void PyInvoker::constructor(const string& className, const string& initVal) 
{
	if (pyClassMap.find(className) != pyClassMap.end()) 
	{
		return;
	}

	PyObject* instance = callFunc(className, "(s)", initVal);

	pyClassMap[className] = instance;
}

PyObject* PyInvoker::callFunc(const string& funcName, const string& format, const string& args)
{
	if (!PyObject_HasAttrString(getModuleRef(), funcName.c_str()))
	{
		PyErr_Print();
		THROW_M(PyException, __func__);
	}


	PyObject* ref = PyObject_CallMethod(getModuleRef(), (char*) funcName.c_str(), (char*) format.c_str(), (char*) args.c_str());
	if (ref == NULL)
	{
		PyErr_Print();
		THROW_M(PyException, __func__);
	}

	return ref;
}


void PyInvoker::run(const string funcName, const string args)
{
	callFunc(funcName, "()", args);
}

void PyInvoker::callMethod(const string& className, const string& methodname, const string& args)
{
	PyObject* method = PyObject_GetAttrString(pyClassMap[className], methodname.c_str());
	if (method == NULL)
	{
		PyErr_Print();
		THROW_M(PyException, __func__);
	}

	PyObject* pyArgs = Py_BuildValue("()");
	if (PyCallable_Check(method)) {
		PyObject_CallObject(method,  pyArgs);
	} else {
		PyErr_Print();
		THROW_M(PyException, __func__);
	}
}
